#include "callother.h"

CoValue *CoList_New(ssize_t size)
{
	CoList *list;
	size_t nbytes;

	if (size < 0) {
        /* TODO: [ERROR] bad internal Call */
		return NULL;
	}

	nbytes = size * sizeof(CoValue *);
	/* Check for overflow */
	if (nbytes / sizeof(CoValue *) != (size_t)size) {
        /* TODO: [ERROR] no memory */
		return NULL;
    }

    list = (CoList *)CO_MALLOC(sizeof(CoList));
    if (list == NULL) {
        /* TODO: [ERROR] no memory */
        return NULL;
    }
    COVALUE_INIT(list, &CoList_Type);

	if (size <= 0) {
		list->l_item = NULL;
    }
    else {
		list->l_item = (CoValue **) CO_MALLOC(nbytes);
		if (list->l_item == NULL) {
            /* TODO: [ERROR] no memory */
            return NULL;
		}
		memset(list->l_item, 0, nbytes);
	}

	list->l_size = size;
	list->l_allocated = size;

	return (CoValue *) list;
}

CoValue *CoList_GetItem(CoValue *self, ssize_t index)
{
	if (!COLIST_CHECK(self)) {
		/* TODO: [ERROR] bad internal call */
		return NULL;
	}
	if (index < 0 || index >= ((CoList *)self) -> l_size) {
		/* TODO: [ERROR] list index out of range */
		return NULL;
	}
	return ((CoList *)self) -> l_item[index];
}

int CoList_SetItem(CoValue * self, ssize_t index, CoValue *newitem)
{
	CoValue *olditem;
	CoValue **p;
	if (!COLIST_CHECK(self)) {
		CO_XDECREF(newitem);
		/* TODO: [ERROR] bad internal call */
		return -1;
	}
	if (index < 0 || index >= ((CoList *)self) -> l_size) {
		CO_XDECREF(newitem);
		/* TODO: [ERROR] list assignment index out of range */
		return -1;
	}
	p = ((CoList *)self) -> l_item + index;
	olditem = *p;
	*p = newitem;
	CO_XDECREF(olditem);
	return 0;
}

/* Ensure l_item has room for at least newsize elements, and set
 * l_size to newsize.  If newsize > l_size on entry, the content
 * of the new slots at exit is undefined heap trash; it's the caller's
 * responsiblity to overwrite them with sane values.
 * The number of allocated elements may grow, shrink, or stay the same.
 * Failure is impossible if newsize <= l_allocated on entry, although
 * that partly relies on an assumption that the system realloc() never
 * fails when passed a number of bytes <= the number of bytes last
 * allocated (the C standard doesn't guarantee this, but it's hard to
 * imagine a realloc implementation where it wouldn't be true).
 * Note that l_item may change, and even if newsize is less
 * than l_size on entry.
 */
static int list_resize(CoList *list, ssize_t newsize)
{
	CoValue **items;
	size_t new_allocated;
	ssize_t allocated = list->l_allocated;

	/* Bypass realloc() when a previous overallocation is large enough
	   to accommodate the newsize.  If the newsize falls lower than half
	   the allocated size, then proceed with the realloc() to shrink the list.
	*/
	if (allocated >= newsize && newsize >= (allocated >> 1)) {
		assert(list->l_item != NULL || newsize == 0);
		list->l_size = newsize;
		return 0;
	}

	/* This over-allocates proportional to the list size, making room
	 * for additional growth.  The over-allocation is mild, but is
	 * enough to give linear-time amortized behavior over a long
	 * sequence of appends() in the presence of a poorly-performing
	 * system realloc().
	 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
	 */
	new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize;
	if (newsize == 0) {
		new_allocated = 0;
    }
	items = list->l_item;
	if (new_allocated <= ((~(size_t)0) / sizeof(CoValue *))) {
		CO_RESIZE(items, CoValue *, new_allocated);
    }
	else {
		items = NULL;
    }
	if (items == NULL) {
        /* TODO: [ERROR] no memory */
		return -1;
	}
	list->l_item = items;
	list->l_size = newsize;
	list->l_allocated = new_allocated;
	return 0;
}

static int app1(CoList *list, CoValue *v)
{
	ssize_t n = list->l_size;

	assert(v != NULL);
	if (n == SSIZE_T_MAX) {
		/* TODO: [ERROR] cannot add more objects to list */
		return -1;
	}

	if (list_resize(list, n+1) == -1)
		return -1;

	CO_INCREF(v);
	list->l_item[n] = v;
	return 0;
}

int CoList_Append(CoValue *self, CoValue *newitem)
{
	if (COLIST_CHECK(self) && (newitem != NULL)) {
		return app1((CoList *)self, newitem);
    }
    /* TODO: [ERROR] bad internal call */
	return -1;
}

void CoList_SortItems(CoValue *self, int (*cmpfunc)(const void*, const void*))
{
    CoList *list;

    assert(COLIST_CHECK(self));
    list = (CoList *)self;

    qsort(list->l_item, list->l_size, sizeof(CoValue *), cmpfunc);
}

static void list_dealloc(CoValue *self)
{
    CoList *list;
	ssize_t index;

    assert(COLIST_CHECK(self));
    list = (CoList *)self;

	if (list->l_item != NULL) {
		/* Do it backwards, for Christian Tismer.
		   There's a simple test case where somehow this reduces
		   thrashing when a *very* large list is created and
		   immediately deleted. */
		index = list->l_size;
		while (--index >= 0) {
			CO_XDECREF(list->l_item[index]);
		}
		CO_FREE(list->l_item);
	}

	CoValue_Free(self);
}

static CoValue *list_iter(CoValue *seq);

CoType CoList_Type = {
    "list",
	list_dealloc,                   /* tp_dealloc */
    0,                              /* tp_hash */
    0,                              /* tp_equal */
    0,                              /* tp_str */
    list_iter,                      /* tp_iter */
    0,                              /* tp_iternext */
};

/*********************** List Iterator **************************/

typedef struct {
	COVALUE_HEAD
	long it_index;
	CoList *it_seq; /* Set to NULL when iterator is exhausted */
} CoListIter;

static CoValue *list_iter(CoValue *);
static void listiter_dealloc(CoValue *);
static CoValue *listiter_next(CoValue *);

CoType CoListIter_Type = {
    "listiterator",                 /* tp_name */
	listiter_dealloc,               /* tp_dealloc */
    0,                              /* tp_hash */
    0,                              /* tp_equal */
    0,                              /* tp_str */
    CoValue_SelfIter,               /* tp_iter */
    listiter_next,                  /* tp_iternext */
};

static CoValue *list_iter(CoValue *seq)
{
	CoListIter *iter;

	if (!COLIST_CHECK(seq)) {
        /* TODO: [ERROR] bad internal Call */
		return NULL;
	}
    iter = (CoListIter *)CO_MALLOC(sizeof(CoListIter));
    if (iter == NULL) {
        /* TODO: [ERROR] no memory */
        return NULL;
    }
    COVALUE_INIT(iter, &CoListIter_Type);

	iter->it_index = 0;
	CO_INCREF(seq);
	iter->it_seq = (CoList *)seq;
	return (CoValue *)iter;
}

static void listiter_dealloc(CoValue *self)
{
    assert(CO_CHECK_TYPE(self, &CoListIter_Type));

	CO_XDECREF(((CoListIter *) self)->it_seq);

	CoValue_Free(self);
}

static CoValue *listiter_next(CoValue *self)
{
    CoListIter *iter;
	CoList *seq;
	CoValue *item;

    assert(CO_CHECK_TYPE(self, &CoListIter_Type));
	assert(self != NULL);
    iter = (CoListIter *)self;

	seq = iter->it_seq;
	if (seq == NULL) {
		return NULL;
    }
	assert(COLIST_CHECK(seq));

	if (iter->it_index < CoList_GET_SIZE(seq)) {
		item = CoList_GET_ITEM(seq, iter->it_index);
		++iter->it_index;
		CO_INCREF(item);
		return item;
	}

	CO_DECREF(seq);
	iter->it_seq = NULL;
	return NULL;
}
